/*
 * Zed Attack Proxy (ZAP) and its related class files.
 * 
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *   
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.zaproxy.zap.extension.brk.impl.http;

import java.util.regex.Pattern;

import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.extension.brk.AbstractBreakPointMessage;
import org.zaproxy.zap.extension.httppanel.Message;

public class HttpBreakpointMessage extends AbstractBreakPointMessage {

	public enum Location {url, request_header, request_body, response_header, response_body};
	public enum Match {contains, regex};
	
    private static final Logger logger = Logger.getLogger(HttpBreakpointMessage.class);
    
    private static final String TYPE = "HTTP";
    
	private String string;
	private Pattern pattern;
	private Location location;
	private Match match;
	private boolean inverse;
	private boolean ignoreCase;

	public HttpBreakpointMessage(String string, Location location, Match match, boolean inverse, boolean ignoreCase) {
		super();
		this.string = string;
		this.location = location;
		this.match = match;
		this.inverse = inverse;
		this.ignoreCase = ignoreCase;
		
		compilePattern();
	}

	@Override
	public String getType() {
	    return TYPE;
	}
	
	public String getString() {
		return string;
	}

	public void setString(String str) {
		this.string = str;
		compilePattern();
	}

	public Pattern getPattern() {
		return pattern;
	}

	public void setPattern(Pattern pattern) {
		this.pattern = pattern;
	}

	public Location getLocation() {
		return location;
	}

	public void setLocation(Location location) {
		this.location = location;
	}

	public Match getMatch() {
		return match;
	}

	public void setMatch(Match match) {
		this.match = match;
		compilePattern();
	}

	public boolean isInverse() {
		return inverse;
	}

	public void setInverse(boolean inverse) {
		this.inverse = inverse;
	}

	public boolean isIgnoreCase() {
		return ignoreCase;
	}

	public void setIgnoreCase(boolean ignoreCase) {
		this.ignoreCase = ignoreCase;
		compilePattern();
	}

	@Override
	public boolean match(Message aMessage, boolean isRequest, boolean onlyIfInScope) {
	    if (aMessage instanceof HttpMessage) {
	        HttpMessage messge = (HttpMessage)aMessage;

            try {
		        String uri = messge.getRequestHeader().getURI().toString();
	
	            if (onlyIfInScope) {
	            	if (! Model.getSingleton().getSession().isInScope(uri)) {
	            		return false;
	            	}
	            }
	        
		        String src = null;
		        switch (location) {
		        case url:	
		        	src = uri;	
		        	break;
		        case request_header:
		        	if (! isRequest) {
		        		return false;
		        	}
		        	src = messge.getRequestHeader().toString();
		        	break;
		        case request_body:
		        	if (! isRequest) {
		        		return false;
		        	}
		        	src = messge.getRequestBody().toString();
		        	break;
		        case response_header:	
		        	if (isRequest) {
		        		return false;
		        	}
		        	src = messge.getResponseHeader().toString();
		        	break;
		        case response_body:
		        	if (isRequest) {
		        		return false;
		        	}
		        	src = messge.getResponseBody().toString();
		        	break;
		        }
		        
		        boolean res;
		        if (Match.contains.equals(this.match)) {
		        	if (ignoreCase) {
			        	res = src.toLowerCase().contains(string.toLowerCase());
		        	} else {
			        	res = src.contains(string);
		        	}
		        	
		        } else {
		        	res = pattern.matcher(src).find();
		        }
		        
		        if (inverse) {
		        	return ! res;
		        } else {
		        	return res;
		        }
		        
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
	    }
	    
		return false;
	}

	private void compilePattern() {
		try {
			if (ignoreCase) {
				pattern = Pattern.compile(string, Pattern.CASE_INSENSITIVE);
			} else {
				pattern = Pattern.compile(string);
			}
		} catch (Exception e) {
			// This wont be a problem if its a 'contains' match
			logger.debug("Potentially invalid regex", e);
		}
	}

    @Override
    public String getDisplayMessage() {
    	return Constant.messages.getString("brk.brkpoint.location." + location.name()) + ": " + 
    			Constant.messages.getString("brk.brkpoint.match." + match.name()) + ": " +
    			(ignoreCase ? Constant.messages.getString("brk.brkpoint.ignorecase.label") : "") +
    			(inverse ? Constant.messages.getString("brk.brkpoint.inverse.label") : "") +
    			string;
    }
    
    @Override
    public boolean equals(Object obj) {
    	if (obj == null) {
            return false;
    	}
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof HttpBreakpointMessage)) {
            return false;
        }
        HttpBreakpointMessage hbm = (HttpBreakpointMessage) obj;
        return this.getString().equals(hbm.getString()) &&
        		this.getLocation().equals(hbm.getLocation()) &&
        		this.getMatch().equals(hbm.getMatch()) &&
        		this.isIgnoreCase() == hbm.isIgnoreCase() &&
        		this.isInverse() == hbm.isInverse();
    }

    @Override
    public int hashCode() {
    	return new HashCodeBuilder(349, 631). // two 'randomly' chosen prime numbers
            append(string).
            append(location).
            append(match).
            append(ignoreCase).
            append(inverse).
            toHashCode();
    }
}
